﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace IronLua
{

    [StructLayout(LayoutKind.Explicit)]
    internal struct Value
    {

        public static readonly Value[] None = new Value[] { };
        public static readonly Value Nil = new Value() { type = ValueType.Nil };
        public static readonly Value True = Value.FromBoolean(true);
        public static readonly Value False = Value.FromBoolean(false);
        public static readonly Value Zero = Value.FromDouble(0d);
        public static readonly Value EmptyString = Value.FromString("");
        public static readonly Value DeadKey = new Value() { type = ValueType.DeadKey };

        public static Value FromInt32(int value)
        {
            return new Value() { Number = (double)value, type = ValueType.Number };
        }

        public static Value FromInt64(long value)
        {
            return new Value() { Number = (double)value, type = ValueType.Number };
        }

        public static Value FromSingle(float value)
        {
            return new Value() { Number = (double)value, type = ValueType.Number };
        }

        public static Value FromDouble(double value)
        {
            return new Value() { Number = value, type = ValueType.Number };
        }

        public static Value FromBoolean(bool value)
        {
            return new Value() { Boolean = value, type = ValueType.Boolean };
        }

        public static Value FromString(string value)
        {
            return new Value() { String = value, type = ValueType.String };
        }

        public static Value FromPrototype(Metadata.Prototype proto)
        {
            return new Value() { Prototype = proto, type = ValueType.Prototype };
        }

        public static Value FromFunction(Closure closure)
        {
            return new Value() { Function = closure, type = ValueType.Function };
        }

        private static Value FromTable(ITable table)
        {
            return new Value { Table = table, type = ValueType.Table };
        }

        public static Value FromThread(Thread value)
        {
            return new Value() { Thread = value, type = ValueType.Thread };
        }

        public static Value FromUserData(object value)
        {
            return new Value() { UserData = new UserData(value), type = ValueType.UserData };
        }

        public static Value FromLightUserData(object value)
        {
            return new Value() { ClrObject = value, type = ValueType.LightUserData };
        }

        private static Value FromClrDictionary(IDictionary<object, object> dictionary)
        {
            return new Value { Table = new Table(dictionary), type = ValueType.Table };
        }

        public static Value FromAny(object arg)
        {
            if (arg == null)
            {
                return Value.Nil;
            }

            var dictionary = arg as IDictionary<object, object>;
            var proto = arg as Metadata.Prototype;
            var thread = arg as Thread;
            var table = arg as ITable;
            var closure = arg as Closure;

            if (dictionary != null)
            {
                // TODO: throw new NotImplementedException("dictionary");
                return Value.FromClrDictionary(dictionary);
            }

            if (proto != null)
            {
                return Value.FromPrototype(proto);
            }

            if (closure != null)
            {
                return Value.FromFunction(closure);
            }

            if (thread != null)
            {
                return Value.FromThread(thread);
            }

            if (table != null)
            {
                return Value.FromTable(table);
            }

            var type = arg.GetType();

            if (type == typeof(bool))
            {
                return Value.FromBoolean((bool)arg);
            }
            else if (type == typeof(string))
            {
                return Value.FromString((string)arg);
            }
            else if (type == typeof(int))
            {
                return Value.FromInt32((int)arg);
            }
            else if (type == typeof(long))
            {
                return Value.FromInt64((long)arg);
            }
            else if (type == typeof(float))
            {
                return Value.FromSingle((float)arg);
            }
            else if (type == typeof(double))
            {
                return Value.FromDouble((double)arg);
            }

            return Value.FromLightUserData(arg);
        }

        [FieldOffset(16)]
        private ValueType type;

        [FieldOffset(0)]
        public bool Boolean;
        [FieldOffset(0)]
        public double Number;

        [FieldOffset(8)]
        public string String;
        [FieldOffset(8)]
        public ITable Table;
        [FieldOffset(8)]
        public Thread Thread;
        [FieldOffset(8)]
        public Metadata.Prototype Prototype;
        [FieldOffset(8)]
        public Closure Function;
        [FieldOffset(8)]
        public UserData UserData;
        [FieldOffset(8)]
        public object ClrObject;
        [FieldOffset(8)]
        public ILuaDynamicObject LuaObject;

        public ValueType    Type { get { return this.type; } }
        public bool         IsNil { get { return this.type == ValueType.Nil; } }
        public bool         IsTable { get { return this.type == ValueType.Table; } }
        public bool         IsFunction { get { return this.type == ValueType.Function; } }
        public bool         IsNumber { get { return this.type == ValueType.Number; } }
        public bool         IsString { get { return this.type == ValueType.String; } }
        public bool         IsPrototype { get { return this.type == ValueType.Prototype; } }
        public bool         IsUserData { get { return this.type == ValueType.UserData; } }
        public bool         IsLightUserData { get { return this.type == ValueType.LightUserData; } }
        public bool         IsDeadKey { get { return this.type == ValueType.DeadKey; } }

        public override string ToString()
        {
            switch (this.type)
            {
                case ValueType.Nil:
                    return "nil";
                case ValueType.Boolean:
                    return this.Boolean ? "true" : "false";
                case ValueType.LightUserData:
                    return (this.UserData ?? (object)"null").ToString();
                case ValueType.Number:
                    return this.Number.ToString(System.Globalization.CultureInfo.InvariantCulture);
                case ValueType.String:
                    return "\"{0}\"".FormatWith(this.String);
                default:
                    return "{0} ({1:x})".FormatWith(this.type.ToString().ToLower(), this.ClrObject.GetHashCode());
            }
        }

        public object ToClrValue()
        {
            switch (this.Type)
            {
                case ValueType.Nil:
                    return null;

                case ValueType.Boolean:
                    return this.Boolean;

                case ValueType.Number:
                    return this.Number;

                case ValueType.String:
                    return this.String;

                case ValueType.Table:
                    return this.Table;

                case ValueType.Function:
                    return this.Function;

                case ValueType.Thread:
                    return this.Thread;

                case ValueType.Prototype:
                    return this.Prototype;

                case ValueType.UpValue:
                    throw new NotImplementedException("upvalue");

                case ValueType.DeadKey:
                    throw new NotImplementedException("deadkey");

                case ValueType.UserData:
                    return this.UserData;

                case ValueType.LightUserData:
                default:
                    return this.ClrObject;
            }
        }

    }

}
